package cn.iocoder.yudao.framework.util;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.business.basic.annotation.DataSelectable;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.AbstractVO;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.BaseVO;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.MemberScopeVO;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.MerchantScopeVO;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.framework.common.util.reflect.ReflectUtils;
import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import jakarta.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.TypeHandler;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;

/**
 * 对象中的绑定注解缓存管理类
 *
 * @author 山野羡民（1032694760@qq.com）
 */
@SuppressWarnings("unused")
@Slf4j
public class MyBatisPlusUtils {
    /**
     * 数据实体类-数据表信息的缓存
     */
    private static final Map<Class<?>, TableInfo> ENTITY_CLASS_CACHE = new HashMap<>();
    /**
     * 保护字段缓存
     */
    private static final Map<String, List<String>> ENCRYPT_FIELD_CACHE = new ConcurrentHashMap<>();

    /**
     * 获取该类对应的表信息
     *
     * @param entityClass 实体类
     * @return 表信息
     */
    @Nonnull
    public static TableInfo newOrGetTableInfo(Class<?> entityClass) {
        TableInfo tableInfo = ENTITY_CLASS_CACHE.get(entityClass);
        if (tableInfo == null) {
            tableInfo = TableInfoHelper.getTableInfo(entityClass);
            if (tableInfo == null) {
                throw new KnownServiceException(entityClass.getSimpleName() + " 不是有效的数据库表实体类");
            }
            ENTITY_CLASS_CACHE.put(entityClass, tableInfo);
        }
        //log.debug("从实体类({})获取数据库表信息为：tableName={}, keyColumn={}, fieldColumns={}", entityClass.getName(), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getFieldList().stream().map(TableFieldInfo::getColumn).toList());
        return tableInfo;
    }

    /**
     * 获取该类对应的表名
     *
     * @param entityClass 实体类
     * @return 表名
     */
    public static String getTableName(Class<?> entityClass) {
        TableInfo tableInfo = newOrGetTableInfo(entityClass);
        return Optional.of(tableInfo)
                .map(TableInfo::getTableName)
                .orElseThrow();
    }

    /**
     * 获取该类对应的字段列名列表
     *
     * @param entityClass 实体类
     * @return 字段列名列表
     */
    public static List<String> getColumns(Class<?> entityClass) {
        TableInfo tableInfo = newOrGetTableInfo(entityClass);
        return Optional.of(tableInfo)
                .map(TableInfo::getFieldList)
                .map(it -> it.stream().map(TableFieldInfo::getColumn).toList())
                .orElseThrow();
    }

    /**
     * 获取该类对应的主键字段列名
     *
     * @param entityClass 实体类
     * @return 字段列名
     */
    public static String getPrimaryKeyColumn(Class<?> entityClass) {
        TableInfo tableInfo = newOrGetTableInfo(entityClass);
        return Optional.of(tableInfo)
                .map(TableInfo::getKeyColumn)
                .orElseThrow();
    }

    /**
     * 获取该类对应的主键字段类型
     *
     * @param entityClass 实体类
     * @return 字段类型
     */
    public static Class<?> getPrimaryKeyFieldType(Class<?> entityClass) {
        TableInfo tableInfo = newOrGetTableInfo(entityClass);
        return Optional.of(tableInfo)
                .map(TableInfo::getKeyType)
                .orElseThrow();
    }

    /**
     * 获取该类对应的删除字段列名
     *
     * @param entityClass 实体类
     * @return 字段名列名
     */
    public static String getDeletedColumn(Class<?> entityClass) {
        TableInfo tableInfo = newOrGetTableInfo(entityClass);
        return Optional.of(tableInfo)
                .map(TableInfo::getLogicDeleteFieldInfo)
                .map(TableFieldInfo::getColumn)
                .orElseThrow();
    }

    /**
     * 获取该类对应的加密字段列名列表
     *
     * @param entityClass 实体类
     * @return 字段列名列表
     */
    @Nonnull
    public static List<String> getEncryptFields(@Nonnull Class<?> entityClass) {
        return ENCRYPT_FIELD_CACHE.computeIfAbsent(entityClass.getName(), k -> {
            List<String> protectFieldList = new ArrayList<>(4);
            for (Field field : ReflectUtils.extractFields(entityClass, TableField.class)) {
                if (!field.getType().isAssignableFrom(String.class)) {
                    continue;
                }
                TableField tableField = field.getAnnotation(TableField.class);
                if (tableField.typeHandler() == null) {
                    continue;
                }
                @SuppressWarnings("rawtypes")
                Class<? extends TypeHandler> typeHandler = tableField.typeHandler();
                if (typeHandler.isAssignableFrom(EncryptTypeHandler.class)) {
                    protectFieldList.add(field.getName());
                    log.debug("监测到加密保护字段：{}.{}", entityClass.getSimpleName(), field.getName());
                }
            }
            return protectFieldList.isEmpty() ? Collections.emptyList() : protectFieldList;
        });
    }

    /**
     * 基于视图对象的 {@link DataSelectable} 注解提取最小集 SELECT 字段，只查询视图对象中声明的字段
     */
    public static <T> void optimizeSelect(QueryWrapper<T> wrapper, Class<T> entityClass, Class<?> voClass) {
        if (wrapper.getSqlSelect() != null || entityClass == null || voClass == null) {
            return;
        }
        try {
            wrapper.select(entityClass, buildSelectPredicate(entityClass, voClass));
            log.debug("[Query@optimizeSelect] ENTITY={}, VO={}, SELECT={}", entityClass.getSimpleName(), voClass.getSimpleName(), wrapper.getSqlSelect());
        } catch (Exception e) {
            log.warn("[Query@optimizeSelect] ENTITY={}, VO={}, error", entityClass.getSimpleName(), voClass.getSimpleName(), e);
        }
    }

    /**
     * 基于视图对象的 {@link DataSelectable} 注解提取最小集 SELECT 字段，只查询视图对象中声明的字段
     */
    public static <T> void optimizeSelect(LambdaQueryWrapper<T> wrapper, Class<T> entityClass, Class<?> voClass) {
        if (wrapper.getSqlSelect() != null || entityClass == null || voClass == null) {
            return;
        }
        try {
            wrapper.select(entityClass, buildSelectPredicate(entityClass, voClass));
            log.debug("[LambdaQuery@optimizeSelect] ENTITY={}, VO={}: {}", entityClass.getSimpleName(), voClass.getSimpleName(), wrapper.getSqlSelect());
        } catch (Exception e) {
            log.warn("[LambdaQuery@optimizeSelect] ENTITY={}, VO={}: error", entityClass.getSimpleName(), voClass.getSimpleName(), e);
        }
    }

    /**
     * 基于视图对象的 {@link DataSelectable} 注解提取最小集 SELECT 字段，只查询视图对象中声明的字段
     */
    public static <T> void optimizeSelect(MPJLambdaWrapper<T> wrapper, Class<T> entityClass, Class<?> voClass) {
        if (wrapper.getSqlSelect() != null || entityClass == null || voClass == null) {
            return;
        }
        try {
            //noinspection deprecation
            wrapper.select(entityClass, buildSelectPredicate(entityClass, voClass));
            log.debug("[MPJLambdaQuery@optimizeSelect] ENTITY={}, VO={}: {}", entityClass.getSimpleName(), voClass.getSimpleName(), wrapper.getSqlSelect());
        } catch (Exception e) {
            log.warn("[MPJLambdaQuery@optimizeSelect] ENTITY={}, VO={}: error", entityClass.getSimpleName(), voClass.getSimpleName(), e);
        }
    }

    /**
     * 构建 SELECT 字段筛选器，只查询视图对象中声明的字段
     */
    private static Predicate<TableFieldInfo> buildSelectPredicate(Class<?> entityClass, Class<?> voClass) {
        TableInfo tableInfo = newOrGetTableInfo(entityClass);
        List<Field> voFields = ReflectUtils.extractAllFields(voClass, true);
        if (voFields.isEmpty()) {
            // 没有任何字段则不构建查询优化
            return tableFieldInfo -> true;
        }
        List<String> entityFieldNameList = tableInfo.getFieldList()
                .stream()
                .map(TableFieldInfo::getField)
                .map(Field::getName)
                .toList();
        List<String> voFieldNameList = voFields.stream()
                .filter(field -> {
                    DataSelectable selectable = field.getAnnotation(DataSelectable.class);
                    return selectable != null;
                })
                .map(Field::getName)
                .toList();
        if (voFieldNameList.isEmpty()) {
            // 没有 @DataSelectable 注解则不构建查询优化
            return tableFieldInfo -> true;
        }
        int superFieldSize;
        if (MemberScopeVO.class.isAssignableFrom(voClass) || MerchantScopeVO.class.isAssignableFrom(voClass)) {
            superFieldSize = 3;
        } else if (BaseVO.class.isAssignableFrom(voClass)) {
            superFieldSize = 2;
        } else if (AbstractVO.class.isAssignableFrom(voClass)) {
            superFieldSize = 1;
        } else {
            superFieldSize = entityFieldNameList.size();
        }
        log.trace("[buildSelectPredicate] ENTITY={}, VO={}, voFieldAvailableSize={}, superFieldSize={}", entityClass.getSimpleName(), voClass.getSimpleName(), voFieldNameList.size(), superFieldSize);
        if (voFieldNameList.size() <= superFieldSize) {
            // 默认基类视图已有一定的注解，继承的子类没有 @DataSelectable 注解也不构建查询优化，以便兼容旧的 POJO 类
            return tableFieldInfo -> true;
        }
        return fieldInfo -> CollUtil.contains(entityFieldNameList, fieldInfo.getField().getName())
                            && CollUtil.contains(voFieldNameList, fieldInfo.getField().getName())
                            && !fieldInfo.isLogicDelete();
    }

}
